/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	CameraRenderPathSettingsLPP.cpp
*
*	Comments	-	See CameraRenderPathSettingsLPP.h
*
**************************************************************************************/
#include "../Include/CameraRenderPathSettingsLPP.h"
#include "../Include/Engine.h"
#include "../Include/WindowView.h"
#include "../Include/GlobalGraphicsStrings.h"
#include "../Include/GraphicsResourceManager.h"
#include "../Include/ComponentCamera.h"
#include "../Include/Scene.h"

// Include render entries
#include "../Include/RenderEntryClearPipeline.h"
#include "../Include/RenderEntryList.h"
#include "../Include/RenderEntryModel.h"
#include "../Include/RenderEntryRenderTarget.h"
#include "../Include/RenderEntryViewport.h"
#include "../Include/RenderEntryClearRenderBuffers.h"
#include "../Include/RenderEntryPresent.h"
#include "../Include/RenderEntryDepthStencilState.h"
#include "../Include/RenderEntryBindParameters.h"
#include "../Include/RenderEntryLight.h"

namespace Pulse
{

	CameraRenderPathSettingsLPP::CameraRenderPathSettingsLPP( ComponentCamera *pCamera )
	{
		m_pCameraOwner = pCamera;

		m_pDepthBuffer = PSX_NULL;
		m_pNormalBuffer = PSX_NULL;
		m_pLightBuffer = PSX_NULL;
		m_pCompositeBuffer = PSX_NULL;

		m_pDSState2n3 = PSX_NULL;

		m_pREViewport = PSX_NULL;

		m_pRERenderTarget1 = PSX_NULL;
		m_pREDepthStencil1 = PSX_NULL;
		m_pREClearPipeline1 = PSX_NULL;
		
		m_pRERenderTarget2 = PSX_NULL;
		m_pREDepthStencil2 = PSX_NULL;
		m_pREBindParams2 = PSX_NULL;
		m_pREClearPipeline2 = PSX_NULL;
		
		m_pRERenderTarget3 = PSX_NULL;
		m_pREDepthStencil3 = PSX_NULL;
		m_pREBindParams3 = PSX_NULL;
		m_pREClearPipeline3 = PSX_NULL;
		
		m_pREPresent = PSX_NULL;
	}

	CameraRenderPathSettingsLPP::~CameraRenderPathSettingsLPP( void )
	{
		Cleanup();
	}

	void CameraRenderPathSettingsLPP::OnRender( RenderView *pView )
	{
		// NOTE: Might move this to camera specific
		pView->AddRenderEntry( m_pREViewport );

		pView->AddRenderEntry( m_pRERenderTarget1 );
		pView->AddRenderEntry( m_pREDepthStencil1 );
		pView->AddRenderEntry( m_pREClearPipeline1 );

		pView->AddRenderEntry( m_pRERenderTarget2 );
		pView->AddRenderEntry( m_pREBindParams2 );
		pView->AddRenderEntry( m_pREDepthStencil2 );
		pView->AddRenderEntry( m_pREClearPipeline2 );

		pView->AddRenderEntry( m_pRERenderTarget3 );
		pView->AddRenderEntry( m_pREDepthStencil3 );
		pView->AddRenderEntry( m_pREBindParams3 );
		pView->AddRenderEntry( m_pREClearPipeline3 );

		//pView->AddRenderEntry( m_pREPresent );
	}

	EErrorCode::Type CameraRenderPathSettingsLPP::UpdateBufferSizes( void )
	{
		Engine *pEngine = Engine::GetInstance();
		Renderer *pRenderer = pEngine->GetRenderer();
		ParameterSystem *pParamSys = pRenderer->GetParameterSystem();
		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();

		// For safety, release buffers
		ReleaseTextureBuffers();

		// Recreate buffers
		// Create buffers
		////////////////////

		WindowView *pWindowView = m_pCameraOwner->GetOwnerUnsafe()->GetScene()->GetWindowViewOwner();
		const SIZE_T textureWidth = pWindowView->GetWidth();
		const SIZE_T textureHeight = pWindowView->GetHeight();

		if ( textureWidth == 0 || textureHeight == 0 )
		{
			return EErrorCode::_ERROR;
		}

		// Create depth buffer with shader resource and depth buffer binding views
		Texture2DDesc depthBufferTexDesc;
		depthBufferTexDesc.SetAsDepthBuffer( (UINT)textureWidth, (UINT)textureHeight, 
			EGraphicsFormat::R24G8_TYPELESS, TRUE );
		
		DepthStencilViewDesc dsvDesc;
		Tex2DInfoDSV tex2DInfoDSV;
		tex2DInfoDSV.mipSlice = 0;
		dsvDesc.Set( EGraphicsFormat::D24_UNORM_S8_UINT, EGraphicsDSVDimension::TEXTURE2D, EDSVReadOnlyFlag::NONE, &tex2DInfoDSV );
		depthBufferTexDesc.SetDepthStencilView( &dsvDesc );

		ShaderResourceViewDesc srvDesc;
		Tex2DInfoSRV tex2DInfoSRV;
		tex2DInfoSRV.mipLevels = 1;
		tex2DInfoSRV.mostDetailedMip = 0;
		srvDesc.Set( EGraphicsFormat::R24_UNORM_X8_TYPELESS, EGraphicsSRVDimension::TEXTURE2D, &tex2DInfoSRV );
		depthBufferTexDesc.SetShaderResourceView( &srvDesc );

		m_pDepthBuffer = pGM->CreateTexture2D( &depthBufferTexDesc );

		if ( m_pDepthBuffer == PSX_NULL )
		{
			PSX_PushError( "Failed to create Light Pre-Pass depth buffer." );
			return EErrorCode::GRAPHICS;
		}

		// Create normal buffer
		Texture2DDesc normalBufferTexDesc;
		normalBufferTexDesc.Set( ETextureUsage::NORMAL, EGraphicsFormat::R16G16B16A16_FLOAT, 
			textureWidth, textureHeight );
		m_pNormalBuffer = pGM->CreateTexture2D( &normalBufferTexDesc );

		if ( m_pNormalBuffer == PSX_NULL )
		{
			PSX_PushError( "Failed to create Light Pre-Pass normal buffer" );
			return EErrorCode::GRAPHICS;
		}

		// Create light buffer
		Texture2DDesc lightBufferTexDesc;
		lightBufferTexDesc.Set( ETextureUsage::NORMAL, EGraphicsFormat::R8G8B8A8_UNORM,
			textureWidth, textureHeight );
		m_pLightBuffer = pGM->CreateTexture2D( &lightBufferTexDesc );

		if ( m_pLightBuffer == PSX_NULL )
		{
			PSX_PushError( "Failed to create Light Pre-Pass light buffer" );
			return EErrorCode::GRAPHICS;
		}

		// Create composit buffer
		// The default is WindowView's render texture
		//Texture2DDesc compositeBufferTexDesc;
		//compositeBufferTexDesc.Set( ETextureUsage::NORMAL, EGraphicsFormat::R8G8B8A8_UNORM,
		//	textureWidth, textureHeight );
		//m_pCompositeBuffer = pGM->CreateTexture2D( &compositeBufferTexDesc );
		// TODO: If this is not the active camera, create our own composite texture...
		m_pCompositeBuffer = pWindowView->GetRenderTexture();
		if ( m_pCompositeBuffer == PSX_NULL )
		{
			PSX_PushError( "Failed to create Light Pre-Pass composite buffer" );
			return EErrorCode::GRAPHICS;
		}
		
		BindTextureBuffers( m_pDepthBuffer, m_pNormalBuffer, m_pLightBuffer, m_pCompositeBuffer );

		// Last step update viewport
		{
			Viewport viewport( 0.0f, 0.0f, (FLOAT)textureWidth, (FLOAT)textureHeight );		
			m_pREViewport->SetViewport( &viewport );
		}

		return EErrorCode::OKAY;
	}

	Texture2D * CameraRenderPathSettingsLPP::GetCompositeTexture( void )
	{
		if ( m_pCompositeBuffer )
		{
			m_pCompositeBuffer->AddRef();
			return m_pCompositeBuffer;
		}

		return PSX_NULL;
	}

	EErrorCode::Type CameraRenderPathSettingsLPP::Initialize( void )
	{
		if ( m_pRERenderTarget1 != PSX_NULL )
		{
			// Already initialized.
			return EErrorCode::OKAY;
		}

		//// HACK: Set viewport.
		//{
		//	const DisplayMode *pDisplaySettings = pEngine->GetDisplaySettings();
		//	RenderEntryViewport viewportEntry;
		//	Viewport viewport( 0, 0, (FLOAT)pDisplaySettings->width, (FLOAT)pDisplaySettings->height );
		//	viewportEntry.SetViewport( &viewport );
		//	pRenderer->ExecuteRenderEntry( &viewportEntry );
		//}

		Engine *pEngine = Engine::GetInstance();
		Renderer *pRenderer = pEngine->GetRenderer();
		ParameterSystem *pParamSys = pRenderer->GetParameterSystem();
		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();

		// Only create the render entries first
		m_pREViewport = new RenderEntryViewport;

		m_pRERenderTarget1 = new RenderEntryRenderTarget;
		m_pREDepthStencil1 = new RenderEntryDepthStencilState;
		m_pREClearPipeline1 = new RenderEntryClearPipeline( TRUE, TRUE, TRUE, TRUE, TRUE );

		m_pRERenderTarget2 = new RenderEntryRenderTarget;
		m_pREDepthStencil2 = new RenderEntryDepthStencilState;
		m_pREBindParams2 = new RenderEntryBindParameters;
		m_pREClearPipeline2 = new RenderEntryClearPipeline( TRUE, TRUE, TRUE, TRUE, TRUE );

		m_pRERenderTarget3 = new RenderEntryRenderTarget;
		m_pREDepthStencil3 = new RenderEntryDepthStencilState;
		m_pREBindParams3 = new RenderEntryBindParameters;
		m_pREClearPipeline3 = new RenderEntryClearPipeline( TRUE, TRUE, TRUE, TRUE, TRUE );

		m_pREPresent = new RenderEntryPresent;

		if ( m_pRERenderTarget1 == PSX_NULL || m_pREDepthStencil1 ==PSX_NULL ||
			m_pRERenderTarget2 == PSX_NULL || m_pREDepthStencil2== PSX_NULL || m_pREBindParams2 == PSX_NULL ||
			m_pRERenderTarget3 == PSX_NULL || m_pREDepthStencil3 == PSX_NULL || m_pREBindParams3 == PSX_NULL ||
			m_pREPresent == PSX_NULL )
		{
			PSX_PushError( "Out of memory. Failed to initialize render entries." );
			return EErrorCode::MEMORY;
		}

		// ? Pass
		{
			m_pREViewport->SetStage( ERenderViewStage::MAIN );
			m_pREViewport->SetStageStep( ERenderViewStageStep::PRE_PROCESS );
			// viewport( 0.0f, 0.0f, (FLOAT)m_pWindowViewOwner->GetWidth(), (FLOAT)m_pWindowViewOwner->GetHeight() );		
			//m_pREViewport->SetViewport( &viewport );
		}

		// Stage 1 - Geometry Pass
		{
			m_pRERenderTarget1->SetStage( ERenderViewStage::GBUFFER );
			m_pRERenderTarget1->SetStageStep( ERenderViewStageStep::PRE_PROCESS );
			m_pRERenderTarget1->SetClearBuffers( TRUE );
			//m_pRERenderTarget1->SetDepthStencilBuffer( m_pDepthBuffer );
			//m_pRERenderTarget1->SetRenderTarget( 0, m_pNormalBuffer );

			m_pREDepthStencil1->SetStage( ERenderViewStage::GBUFFER );
			m_pREDepthStencil1->SetStageStep( ERenderViewStageStep::PRE_PROCESS );
			m_pREDepthStencil1->SetDepthStencilState( PSX_NULL ); // default depth stencil state
		
			m_pREClearPipeline1->SetStage( ERenderViewStage::GBUFFER );
			m_pREClearPipeline1->SetStageStep( ERenderViewStageStep::POST_PROCESS );
		}

		// Stage 2 - Lighting Pass
		{	
			m_pRERenderTarget2->SetStage( ERenderViewStage::LIGHTING );
			m_pRERenderTarget2->SetStageStep( ERenderViewStageStep::PRE_PROCESS );
			m_pRERenderTarget2->SetClearBuffers( TRUE );
			//m_pRERenderTarget2->SetRenderTarget( 0, m_pLightBuffer );
		
			m_pREDepthStencil2->SetStage( ERenderViewStage::LIGHTING );
			m_pREDepthStencil2->SetStageStep( ERenderViewStageStep::PRE_PROCESS );
			m_pREDepthStencil2->SetDepthStencilState( m_pDSState2n3 );

			m_pREBindParams2->SetStage( ERenderViewStage::LIGHTING );
			m_pREBindParams2->SetStageStep( ERenderViewStageStep::PRE_PROCESS );

			// Set and add binders
			{
				ParameterShaderResource *pShaderRes;
				IParameterBinder *pParamBinder;
			
				// Depth Buffer
				pShaderRes = pParamSys->GetShaderResource( GFX_LP_DEPTH_BUFFER, EShaderResource::TEXTURE2D );
				pParamBinder = m_pREBindParams2->AddParameter( pShaderRes );
				//pParamBinder->SetAsShaderResource( m_pDepthBuffer );

				// GBuffer
				pShaderRes = pParamSys->GetShaderResource( GFX_LP_GBUFFER, EShaderResource::TEXTURE2D );
				pParamBinder = m_pREBindParams2->AddParameter( pShaderRes );
				//pParamBinder->SetAsShaderResource( m_pNormalBuffer );
			}

			m_pREClearPipeline2->SetStage( ERenderViewStage::LIGHTING );
			m_pREClearPipeline2->SetStageStep( ERenderViewStageStep::POST_PROCESS );
		}

		// Stage 3 - Composite Pass
		{
			m_pRERenderTarget3->SetStage( ERenderViewStage::COMPOSITE );
			m_pRERenderTarget3->SetStageStep( ERenderViewStageStep::PRE_PROCESS );
			m_pRERenderTarget3->SetClearBuffers( TRUE );
			//m_pRERenderTarget3->SetRenderTarget( 0, m_pCompositeBuffer );
			
			m_pREDepthStencil3->SetStage( ERenderViewStage::LIGHTING );
			m_pREDepthStencil3->SetStageStep( ERenderViewStageStep::PRE_PROCESS );
			m_pREDepthStencil3->SetDepthStencilState( m_pDSState2n3 );

			m_pREBindParams3->SetStage( ERenderViewStage::COMPOSITE );
			m_pREBindParams3->SetStageStep( ERenderViewStageStep::PRE_PROCESS );

			// Set and add binders for stage 3
			{
				ParameterShaderResource *pShaderRes;
				IParameterBinder *pParamBinder;

				pShaderRes = pParamSys->GetShaderResource( GFX_LP_LIGHT_BUFFER, EShaderResource::TEXTURE2D );
				pParamBinder = m_pREBindParams3->AddParameter( pShaderRes );
				//pParamBinder->SetAsShaderResource( m_pLightBuffer );
			}

			m_pREClearPipeline3->SetStage( ERenderViewStage::COMPOSITE );
			m_pREClearPipeline3->SetStageStep( ERenderViewStageStep::POST_PROCESS );
		}

		// TODO: Middle pass to transfer composite to final buffer.
		//	A separate system should probably be created to handle this task.

		// HACK: Present
		m_pREPresent->SetStage( ERenderViewStage::POST_PROCESS );

		return EErrorCode::OKAY;
	}

	void CameraRenderPathSettingsLPP::Cleanup( void )
	{
		ReleaseTextureBuffers();

		PSX_SafeRelease( m_pDSState2n3 );

		PSX_SafeDelete( m_pREViewport );
		//
		PSX_SafeDelete( m_pRERenderTarget1 );
		PSX_SafeDelete( m_pREDepthStencil1 );
		PSX_SafeDelete( m_pREClearPipeline1 );

		PSX_SafeDelete( m_pRERenderTarget2 );
		PSX_SafeDelete( m_pREDepthStencil2 );
		PSX_SafeDelete( m_pREBindParams2 );
		PSX_SafeDelete( m_pREClearPipeline2 );

		PSX_SafeDelete( m_pRERenderTarget3 );
		PSX_SafeDelete( m_pREDepthStencil3 );
		PSX_SafeDelete( m_pREBindParams3 );
		PSX_SafeDelete( m_pREClearPipeline3 );

		PSX_SafeDelete( m_pREPresent );
	}

	void CameraRenderPathSettingsLPP::ReleaseTextureBuffers( void )
	{
		Engine *pEngine = Engine::GetInstance();
		Renderer *pRenderer = pEngine->GetRenderer();
		ParameterSystem *pParamSys = pRenderer->GetParameterSystem();

		// Before releasing, remove references first
		BindTextureBuffers( PSX_NULL, PSX_NULL, PSX_NULL, PSX_NULL );

		// TODO: References in the graphics pipeline


		// Now release
		PSX_SafeRelease( m_pDepthBuffer );
		PSX_SafeRelease( m_pNormalBuffer );
		PSX_SafeRelease( m_pLightBuffer );
		PSX_SafeRelease( m_pCompositeBuffer );
	}

	void CameraRenderPathSettingsLPP::BindTextureBuffers( Texture2D *pDepthBuffer, Texture2D *pNormalBuffer,
		Texture2D *pLightBuffer, Texture2D *pCompositeBuffer )
	{
		Engine *pEngine = Engine::GetInstance();
		Renderer *pRenderer = pEngine->GetRenderer();
		ParameterSystem *pParamSys = pRenderer->GetParameterSystem();

		// Before releasing, remove references first
		
		// References in the render entries
		// Stage 1 - Geometry Pass
		{
			m_pRERenderTarget1->SetDepthStencilBuffer( m_pDepthBuffer );
			m_pRERenderTarget1->SetRenderTarget( 0, pNormalBuffer );
		}

		// Stage 2 - Lighting Pass
		{	
			m_pRERenderTarget2->SetRenderTarget( 0, pLightBuffer );
		
			// Set and add binders
			{
				ParameterShaderResource *pShaderRes;
				IParameterBinder *pParamBinder;
			
				// Depth Buffer
				pShaderRes = pParamSys->GetShaderResource( GFX_LP_DEPTH_BUFFER, EShaderResource::TEXTURE2D );
				pParamBinder = m_pREBindParams2->FindParameterBinder( pShaderRes );
				
				if ( pParamBinder )
				{
					pParamBinder->SetAsShaderResource( pDepthBuffer );
				}
				else
				{
					PSX_PushError( "Binder does not exist!" );
				}

				// GBuffer
				pShaderRes = pParamSys->GetShaderResource( GFX_LP_GBUFFER, EShaderResource::TEXTURE2D );
				pParamBinder = m_pREBindParams2->FindParameterBinder( pShaderRes );
				
				if ( pParamBinder )
				{
					pParamBinder->SetAsShaderResource( pNormalBuffer );
				}
				else
				{
					PSX_PushError( "Binder does not exist!" );
				}
			}
		}

		// Stage 3 - Composite Pass
		{
			m_pRERenderTarget3->SetRenderTarget( 0, pCompositeBuffer );
			
			// Set and add binders for stage 3
			{
				ParameterShaderResource *pShaderRes;
				IParameterBinder *pParamBinder;

				pShaderRes = pParamSys->GetShaderResource( GFX_LP_LIGHT_BUFFER, EShaderResource::TEXTURE2D );
				pParamBinder = m_pREBindParams3->FindParameterBinder( pShaderRes );
				
				if ( pParamBinder )
				{
					pParamBinder->SetAsShaderResource( pLightBuffer );
				}
				else
				{
					PSX_PushError( "Binder does not exist!" );
				}
			}
		}
	}
}